// src/evaluator/tests.rs
use std::sync::Arc;
use crate::evaluator::{eval, NULL};
use crate::object::{Boolean, Error, Integer, Null, Object};
use crate::lexer::Lexer;
use crate::parser::{self, Parser};
use std::any::Any;
use std::ptr;

#[test]
fn it_works() {
    assert_eq!(2 + 2, 4);
}

#[test]
fn test_eval_integer_expression(){
    struct EvalIntegerExp{
        input: String,
        expected: i64,
    }
    let tests = vec![
        EvalIntegerExp{
            input: "-5".to_string(),
            expected: -5,
        },
        EvalIntegerExp{
            input: "-10".to_string(),
            expected: -10,
        },
        EvalIntegerExp{
            input: "5".to_string(),
            expected: 5,
        },
        EvalIntegerExp{
            input: "10".to_string(),
            expected: 10,
        },
        EvalIntegerExp{
            input: "5+5+5+5-10".to_string(),
            expected: 10,
        },
        EvalIntegerExp{
            input: "3*(3*3)+10".to_string(),
            expected: 37,
        }
    ];
    for tt in tests{
        let evaluated = test_eval(tt.input);
        test_integer_object(evaluated.as_ref(), tt.expected);
    }
}

fn test_eval(input: String)->Arc<dyn Object>{
    let l = Lexer::new(input);
    let mut p = Parser::new(l);
    let program = p.parse_program();
    // 检查是否有解析错误
    let errors = p.get_errors();
    if !errors.is_empty() {
        for msg in errors {
            // writeln!(writer, "\t{}", msg)?;
            eprintln!("\t{}", msg);
        }
        Arc::from(NULL)
    }else{
        eval(&program)
    }
}

fn test_integer_object(obj: &dyn Object, expected: i64) -> bool{
    if let Some(o) = obj.as_any().downcast_ref::<Integer>(){
        if o.value != expected{
            eprintln!("object has wrong value, got {}, expected {}", o.value, expected);
            return false;
        }
    }else{
        eprintln!("object expected integer, got {}", obj.get_type());
        return false;
    }
    true
    // unimplemented!()
}

#[test]
fn test_eval_boolean_expression(){
    struct EvalBooleanExp{
        input: String,
        expected: bool,
    }
    let tests = vec![
        EvalBooleanExp{
            input: "true".to_string(),
            expected: true,
        },
        EvalBooleanExp{
            input: "false".to_string(),
            expected: false,
        },
        EvalBooleanExp{
            input: "true == true".to_string(),
            expected: true,
        },
        EvalBooleanExp{
            input: "true != false".to_string(),
            expected: true,
        },
        EvalBooleanExp{
            input: "true == false".to_string(),
            expected: false,
        },
        EvalBooleanExp{
            input: "false != false".to_string(),
            expected: false,
        },
        EvalBooleanExp{
            input: "(1>2) == true".to_string(),
            expected: false,
        },
        EvalBooleanExp{
            input: "(1>2) == false".to_string(),
            expected: true,
        },
        EvalBooleanExp{
            input: "(1<2) == false".to_string(),
            expected: false,
        },
        EvalBooleanExp{
            input: "(1<2) == true".to_string(),
            expected: true,
        }
    ];
    for tt in tests{
        let evaluated = test_eval(tt.input.clone());
        eprintln!("tt.input=={},tt.expected={} ", tt.input, tt.expected);
        assert_eq!(test_boolean_object(evaluated.as_ref(), tt.expected), true);
        // if let Some(o) = evaluated {
        //     test_boolean_object(o.as_ref(), tt.expected);
        // }else{
        //     panic!("error: got nil, expected boolean");
        // }
        
    }
}

fn test_boolean_object(obj: &dyn Object, expected: bool) -> bool{
    if let Some(o) = obj.as_any().downcast_ref::<Boolean>(){
        if o.value != expected{
            eprintln!("object has wrong value, got {}, expected {}", o.value, expected);
            return false;
        }
    }else{
        eprintln!("object expected boolean, got {}", obj.get_type());
        return false;
    }
    true
}

#[test]
fn test_bang_operator(){
    struct BangOperator<'a>{
        input: &'a str,
        expected: bool,
    }
    let tests = vec![
        BangOperator{
            input: "!true",
            expected: false,
        },
        BangOperator{
            input: "!false",
            expected: true,
        },
        BangOperator{
            input: "!5",
            expected: false,
        },
        BangOperator{
            input: "!!true",
            expected: true,
        },
        BangOperator{
            input: "!!false",
            expected: false,
        },
        BangOperator{
            input: "!!5",
            expected: true,
        },
        
    ];
    for tt in tests{
        // if let Some(evluated) = test_eval(tt.input.to_string()){
        //     assert_eq!(test_boolean_object( &*evluated , tt.expected), true);
        // }else{
        //     panic!("test eval returns a null object");
        // }
        let evluated = test_eval(tt.input.to_string());
        assert_eq!(test_boolean_object( evluated.as_ref() , tt.expected), true);
    }
}

fn test_null_object(obj: &dyn Object) -> bool{
    if ptr::eq(obj as *const dyn Object, (&NULL) as *const dyn Object){
    // if !Arc::ptr_eq(&Arc::from(obj), &Arc::from(&NULL)){
        eprintln!("object is not null, got {}", obj.get_type());
        return  false;
    }
    true
}

#[test]
fn test_if_else_expression(){
    struct IfElse<'a>{
        input: &'a str,
        expected: Option<&'a dyn Any>,
    }
    let tests = vec![
        IfElse{
            input: "if(true){10}",
            expected: Some(&10),
        },
        IfElse{
            input: "if(false){10}",
            expected: None,
        },
        IfElse{
            input: "if(1<2){10}",
            expected: Some(&10),
        },
        IfElse{
            input: "if(1>2){10}else{100}",
            expected: None,
        }
    ];
    for test in tests{
        let evaluated = test_eval(test.input.to_string());
        // eprintln!("test.expected={:?}", test.expected);
        match test.expected{
            Some(t) =>{
                if let Some(integer) = t.downcast_ref::<i64>(){
                    assert_eq!(test_integer_object(&*evaluated, *integer), true);
                }else if let Some(integer) = t.downcast_ref::<i32>(){
                    assert_eq!(test_integer_object(&*evaluated, *integer as i64), true);
                }else if let Some(integer) = t.downcast_ref::<isize>(){
                    assert_eq!(test_integer_object(&*evaluated, *integer as i64), true);
                }
                
                else{
                    assert_eq!(test_null_object(&*evaluated), true);
                }
            },
            None=>{
                assert_eq!(test_null_object(&*evaluated), true);
            }
        }
        // if let Some(t) = test.expected{
        //     if let Some(integer) = t.downcast_ref::<i64>(){
        //         assert_eq!(test_integer_object(&*evaluated, *integer), true);
        //     }else if let Some(integer) = t.downcast_ref::<i32>(){
        //         assert_eq!(test_integer_object(&*evaluated, *integer as i64), true);
        //     }else if let Some(integer) = t.downcast_ref::<isize>(){
        //         assert_eq!(test_integer_object(&*evaluated, *integer as i64), true);
        //     }
            
        //     else{
        //         assert_eq!(test_null_object(&*evaluated), true);
        //     }
        // }else{
        //     eprintln!("get type :{}, value  = {}", evaluated.get_type(), evaluated.inspect());
        //     assert_eq!(test_null_object(&*evaluated), true);
        // }
        
    }
}

#[test]
fn test_return_statment(){
    struct RtStmt<'a>{
        input: &'a str,
        expected: i64,
    }

    let tests = vec![
        RtStmt{
            input: "if(10>1){
                if(10>1){
                    return 10;
                }
                return 1;
            }",
            expected: 10,
        }
    ];
    for tt in tests{
        let evaluated = test_eval(tt.input.to_string());
        assert_eq!(test_integer_object(evaluated.as_ref(), tt.expected), true);
    }
}

#[test]
fn test_error_handling(){
    struct ErrorHandling<'a>{
        input: &'a str,
        expected: &'a str,
    }
    let tests = vec![
        ErrorHandling{
            input: "5+true",
            expected: "type mismatch: INTEGER + BOOLEAN",
        },
        ErrorHandling{
            input: "5+true;5;",
            expected: "type mismatch: INTEGER + BOOLEAN",
        },
        ErrorHandling{
            input: "-true;",
            expected: "unknown operator: -BOOLEAN",
        },
        ErrorHandling{
            input: "true+false",
            expected: "unknown operator: BOOLEAN + BOOLEAN",
        },
        ErrorHandling{
            input: "5;true+false;5",
            expected: "unknown operator: BOOLEAN + BOOLEAN",
        },
        ErrorHandling{
            input: "if(10>1){true+false;}",
            expected: "unknown operator: BOOLEAN + BOOLEAN",
        },
        ErrorHandling{
            input: "if(10>1){
                if(10>1){
                return true + false;
        }
        return 2;
            }",
            expected: "unknown operator: BOOLEAN + BOOLEAN",
        },
        ErrorHandling{
            input: "foobar",
            expected: "identifier not found: foobar",
        }
    ];
    for tt in tests{
        let evaluated = test_eval(tt.input.to_string());
        if let Some(t) = evaluated.as_any().downcast_ref::<Error>(){
            eprintln!("t.message={}, tt.expected={}", t.message, tt.expected);
            assert_eq!(t.message, tt.expected);
        }else{
            panic!("no error object returned, got {}", evaluated.get_type());
        }
    }
}

#[test]
fn test_let_statements(){
    struct LetStmt<'a>{
        input: &'a str,
        expected: i64,
    }
    let tests = vec![
        LetStmt{
            input: "let a=5;a;",
            expected: 5,
        },
        LetStmt{
            input: "let a = 5 * 5; a; ",
            expected: 25,
        },
        LetStmt{
            input: "let a = 5; let b = a; b;",
            expected: 5,
        },
        LetStmt{
            input: "let a = 5; let b = a; let c = a + b + c; c ;",
            expected: 15,
        }
    ];
    for tt in tests{
        assert_eq!(test_integer_object(test_eval(tt.input.to_string()).as_ref(), tt.expected), true);
    }
}